<template>
    <div class="w-screen h-screen" ref="threeCotainer"></div>

    <div class="base-description">基于three.js的3D模型展示</div>
</template>

<script setup lang="ts">
import {
    AmbientLight,
    AnimationMixer,
    AxesHelper,
    Clock,
    InstancedMesh,
    Matrix4,
    Mesh,
    MeshLambertMaterial,
    PCFSoftShadowMap,
    PerspectiveCamera,
    Raycaster,
    Scene,
    SphereGeometry,
    Vector2,
    WebGLRenderer,
} from 'three'

import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
import { gsap } from 'gsap'

import { ref, onMounted } from 'vue'

const threeCotainer = ref<HTMLDivElement>()

const statsDom = ref<HTMLDivElement>()

onMounted(async () => {
    // 场景
    const scene = new Scene()

    // 摄像机 (视角, 宽高比, 近裁剪面, 远裁剪面)
    const camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000)
    // 摄像机位置(坐标)
    camera.position.set(0, 0, 8)
    // 摄像机朝向
    camera.lookAt(scene.position)

    // 渲染器
    const renderer = new WebGLRenderer({
        // 抗锯齿
        antialias: true,
    })
    // 渲染器尺寸
    renderer.setSize(window.innerWidth, window.innerHeight)
    // 允许渲染器产生阴影
    renderer.shadowMap.enabled = true
    // 设置阴影的分辨率
    renderer.shadowMap.type = PCFSoftShadowMap
    // 设置背景颜色
    renderer.setClearColor('deepskyblue')

    // 设置渲染器的像素比
    renderer.setPixelRatio(window.devicePixelRatio)

    // 将渲染器的dom元素添加到页面中
    threeCotainer.value?.appendChild(renderer.domElement)

    const fbxloader = new FBXLoader()

    // 加载人物外骨骼模型
    const object = await fbxloader.loadAsync('./assets/Angry.fbx')
    const mixer = new AnimationMixer(object)
    const action = mixer.clipAction(object.animations[0])
    object.name = '吴昌焕的模型'
    object.position.set(3, -3, 0)
    object.scale.set(0.03, 0.03, 0.03)
    object.rotation.set(0, Math.cos(10) * 8, 0)
    action.play()
    object.traverse(function (child) {
        // @ts-ignore
        if (child.isMesh) {
            child.castShadow = true
            child.receiveShadow = true
        }
    })

    scene.add(object)

    //  球体
    const sphereGeometry = new SphereGeometry(4, 20, 20)
    const sphereMaterial = new MeshLambertMaterial({
        color: 'white',
    })
    const sphere = new Mesh(sphereGeometry, sphereMaterial)
    for (let j = 0; j < 10; j++) {
        let pointInstance = new InstancedMesh(sphere.geometry, sphere.material, 100)
        for (let i = 0; i < 100; i++) {
            let x = Math.random() * 1000 - 500
            let y = Math.random() * 1000 - 500
            let z = Math.random() * 1000 - 500

            let matrix = new Matrix4()
            let size = Math.random() * 20 - 8
            matrix.makeScale(size, size, size)
            matrix.makeTranslation(x, y, z)
            pointInstance.setMatrixAt(i, matrix)
        }

        gsap.to(pointInstance.position, {
            duration: Math.random() * 10 + 5,
            z: -1000,
            ease: 'linear',
            repeat: -1,
        })
        scene.add(pointInstance)
    }

    // 环境光
    const ambientLight = new AmbientLight(0xffffff, 1.5)
    scene.add(ambientLight)

    const clock = new Clock()
    // 渲染函数
    const renderFuc = () => {
        requestAnimationFrame(renderFuc)
        // 模型动画
        if (mixer) mixer.update(clock.getDelta())
        renderer.render(scene, camera)
    }
    renderFuc()

    // 监听窗口变化
    window.addEventListener('resize', () => {
        // 摄像机宽高比
        camera.aspect = window.innerWidth / window.innerHeight
        // 摄像机更新投影矩阵
        camera.updateProjectionMatrix()
        // 渲染器尺寸
        renderer.setSize(window.innerWidth, window.innerHeight)
    })

    // 点击事件
    window.addEventListener('click', event => {
        const raycaster = new Raycaster()
        const mouse = new Vector2()
        mouse.x = (event.clientX / window.innerWidth) * 2 - 1
        mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
        // 通过摄像机和鼠标位置更新射线
        raycaster.setFromCamera(mouse, camera)
        // 计算物体和射线的焦点
        const intersects = raycaster.intersectObjects(scene.children)
        // 如果有焦点
        if (intersects.length > 0) {
            // 获取点击的物体
            const clickedObject = intersects[0].object
            // 点击的是模型
            if (clickedObject instanceof Mesh) {
                // 点击的是模型的子物体,向上递归查找模型
                const name = clickedObject.parent?.parent?.name
                if (name) {
                    clickMap[name]()
                }
            }
        }
    })
})

const clickMap: { [x: string]: () => void } = {
    吴昌焕的模型: () => {
        console.log('点击了吴昌焕的模型')
    },
}
</script>

<style lang="less" scoped></style>
